#include <stdio.h>
#include <string.h>
#include <libmpd/libmpd.h>
#include <gtk/gtk.h>

#include "mpdmodel.h"

static GType column_types[DB_COLUMN_MAX]={
	G_TYPE_STRING,
	G_TYPE_STRING,
};

static void list_free(GList *list)
{
	g_list_foreach(list, (GFunc)g_free, NULL);
	g_list_free(list);
}

static char* parent_path(const char *path)
{
	if(!path || !strlen(path))
		return NULL;

	char *result = NULL;
	char **tokens = g_strsplit(path, "/", -1);
	char **it = tokens;
	while(*(it+1))
		it++;
	g_free(*it);
	*it = 0;
	result = g_strjoinv("/", tokens);
	g_strfreev(tokens);

	return result;
}

static GType
mpdmodel_get_column_type(GtkTreeModel *model, int column)
{
	return column_types[CLAMP(column, 0, G_N_ELEMENTS(column_types))];
}

static int
mpdmodel_get_n_columns(GtkTreeModel *model)
{
	return DB_COLUMN_MAX;
}

static GList* mpdmodel_get_dir(MpdModel *mpdmodel, const char* path)
{
	GList *result = NULL;
	
	MpdData *dlist = dlist = mpd_database_get_directory(mpdmodel->mpd, (char*)path);
	while(dlist){
		char *name = NULL;
		if(dlist->type == MPD_DATA_TYPE_DIRECTORY){
			name = dlist->directory;
		}else if(dlist->type == MPD_DATA_TYPE_SONG){
			name = dlist->song->file;
		}
		GQuark q = g_quark_from_string(name);
		g_dataset_id_set_data(mpdmodel, q, (gpointer)dlist->type);
		if(name)
			result = g_list_append(result, g_strdup(name));
		dlist = mpd_data_get_next(dlist);
	}
	return result;
}

static gboolean
mpdmodel_get_iter(GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
{
	gboolean result = TRUE;
	MpdModel *mpdmodel = MPDMODEL(model);
	gint cols = gtk_tree_path_get_depth(path);
	gint *indices = gtk_tree_path_get_indices(path);
	char *dirs[cols+2];

	memset(dirs, 0, sizeof(dirs));

	for(int i=0; i<cols; i++){
		char *dir = g_build_pathv("/", dirs);
		GList *contents = mpdmodel_get_dir(mpdmodel, dir);
		dirs[i] = g_path_get_basename(g_list_nth_data(contents, indices[i]));
		list_free(contents);
		g_free(dir);
		if(dirs[i] == NULL){
			result = FALSE;
			goto error;
		}
	}

	iter->stamp = sizeof(mpdmodel);
	char *iter_path = g_strjoinv("/", dirs);
	iter->user_data = (gpointer)g_quark_from_string(iter_path);
	g_free(iter_path);
	iter->user_data2 = (gpointer)indices[cols-1];

 error:
	for(char **it = dirs; *it; it++)
		g_free(*it);
	return result;
}

static gboolean
mpdmodel_iter_has_child(GtkTreeModel *model, GtkTreeIter *iter)
{
	gboolean result = FALSE;
	char *path = (char*)g_quark_to_string((int)iter->user_data);

	GList *list = mpdmodel_get_dir(MPDMODEL(model), path);
	result = list != NULL;
	list_free(list);

	return result;
}

static gboolean
mpdmodel_iter_next(GtkTreeModel *model, GtkTreeIter *iter)
{
	gboolean result = FALSE;
	int index = (int)iter->user_data2 + 1;
	const char *dir = g_quark_to_string((int)iter->user_data);
	if(!dir)
		return FALSE;

	dir = parent_path(dir);
	GList *contents = mpdmodel_get_dir(MPDMODEL(model), dir);
	g_free((char*)dir);

	if(g_list_length(contents) > index ){
		char *path = g_list_nth_data(contents, index);
		iter->user_data = (gpointer)g_quark_from_string(path);
		iter->user_data2 = (gpointer)index;
		result = TRUE;
	}
	list_free(contents);
	
	return result;
}

static gboolean
mpdmodel_iter_children(GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent)
{
	const char *parent_path = g_quark_to_string((int)parent->user_data);
	GList *dirs = mpdmodel_get_dir(MPDMODEL(model), parent_path);
	if(!dirs)
		return FALSE;

	char *path = g_strdup(dirs->data);
	list_free(dirs);
	iter->user_data = (gpointer)g_quark_from_string(path);
	iter->user_data2 = 0;

	return TRUE;
}

int list_string_pos(GList *list, const char* name)
{
	for(int pos = 0; list; list = list->next, pos++){
		if(strcmp(g_basename(list->data), name) == 0)
			return pos;
	}
	puts("pos not found");
	return -1;
}

static gboolean
mpdmodel_iter_parent(GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child)
{
	const char *child_path = g_quark_to_string((int)child->user_data);
	if(!strchr(child_path, '/'))
		return FALSE;

	char *path = parent_path(child_path);
	GQuark q = g_quark_from_string(path);
	iter->user_data = (gpointer)q;

	GList *contents = mpdmodel_get_dir(MPDMODEL(model), parent_path(path));
	iter->user_data2 = (gpointer)list_string_pos(contents, g_basename(path));
	list_free(contents);

	return TRUE;
}

static void
mpdmodel_get_value(GtkTreeModel *model, GtkTreeIter *iter, int column, GValue *value)
{
	char *path = (char*)g_quark_to_string((int)iter->user_data);
	g_value_init(value, G_TYPE_STRING);
	if(column == DB_COLUMN_PATH)
		g_value_set_string(value, path);
	else if(column == DB_COLUMN_NAME)
		g_value_set_string(value, g_basename(path));
}

static gboolean
mpdmodel_iter_nth_child(GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent, int n)
{
	gboolean result = FALSE;
	const char *path = NULL;
	if(parent)
		path = g_quark_to_string((int)parent->user_data);

	GList *contents = mpdmodel_get_dir(MPDMODEL(model), path);
	if(n < g_list_length(contents)){
		char *cpath = g_list_nth_data(contents, n);
		iter->user_data = (gpointer)g_quark_from_string(cpath);
		iter->user_data = (gpointer)n;
	}
	list_free(contents);

	return result;
}


static GtkTreePath*
mpdmodel_get_path(GtkTreeModel *model, GtkTreeIter *iter)
{
	GtkTreeIter current = *iter;
	GtkTreeIter parent;
	GtkTreePath *retval = gtk_tree_path_new_from_indices((int)iter->user_data2, -1);

	while(gtk_tree_model_iter_parent(model, &parent, &current)){
		current = parent;
		gtk_tree_path_append_index(retval, (int)current.user_data2);
	}
	
	return retval;
}

static void 
mpdmodel_finalize(GObject *obj)
{
}

static void
mpdmodel_class_init(MpdModelClass *class)
{
	G_OBJECT_CLASS(class)->finalize = mpdmodel_finalize;
}

static void
mpdmodel_init(MpdModel *mpdmodel)
{
}

static gboolean 
mpdmodel_row_draggable(GtkTreeDragSource *drag_source, GtkTreePath *path)
{
	return TRUE;
}

static gboolean 
mpdmodel_drag_data_get(GtkTreeDragSource *drag_source, GtkTreePath *path, GtkSelectionData *selection)
{
	MpdModel *mpdmodel = MPDMODEL(drag_source);
	GtkTreeIter iter;

	if(!mpdmodel_get_iter(GTK_TREE_MODEL(mpdmodel), &iter, path))
		return FALSE;

	GValue value;
	bzero(&value, sizeof(value));
	mpdmodel_get_value(GTK_TREE_MODEL(mpdmodel), &iter, DB_COLUMN_PATH, &value);
	const char *db_path = g_value_get_string(&value);

	selection->target = gdk_atom_intern("splayer-db-path", TRUE);
	selection->format = sizeof(char);
	selection->data = (guchar*)g_strdup(db_path);
	selection->length = strlen(db_path);

	g_value_unset(&value);
	
	return TRUE;
}

static void
mpdmodel_drag_source_init(GtkTreeDragSourceIface *iface)
{
	iface->row_draggable = mpdmodel_row_draggable;
	iface->drag_data_get = mpdmodel_drag_data_get;
	iface->drag_data_delete = mpdmodel_row_draggable;
}

static void 
mpdmodel_tree_model_init(GtkTreeModelIface *iface)
{
	iface->get_n_columns = mpdmodel_get_n_columns;
	iface->get_column_type = mpdmodel_get_column_type;
	iface->get_iter = mpdmodel_get_iter;
	iface->iter_has_child  = mpdmodel_iter_has_child;
	iface->iter_next = mpdmodel_iter_next;
	iface->get_value = mpdmodel_get_value;
	iface->iter_children = mpdmodel_iter_children;
	iface->iter_parent = mpdmodel_iter_parent;
	iface->iter_nth_child = mpdmodel_iter_nth_child;
	iface->get_path = mpdmodel_get_path;
}

MpdModel* mpdmodel_new(MpdObj* mpd)
{
	MpdModel *result = g_object_new(TYPE_MPDMODEL, NULL);
	result->mpd = mpd;
	return result;
}

G_DEFINE_TYPE_WITH_CODE(MpdModel, mpdmodel, G_TYPE_OBJECT, 
						G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, mpdmodel_tree_model_init)
						G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_DRAG_SOURCE, mpdmodel_drag_source_init));
